package com.crpc.core.config;

import cn.hutool.core.util.ObjectUtil;
import com.crpc.common.utils.IdGenerator.IdGenerationStrategy;
import com.crpc.core.compress.Compressor;
import com.crpc.core.compress.CompressorFactory;
import com.crpc.core.discovery.RegistryConfig;
import com.crpc.core.loadBalance.LoadBalance;
import com.crpc.core.serialize.Serializer;
import com.crpc.core.serialize.SerializerFactory;
import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import java.io.InputStream;
import java.util.ArrayList;

@Slf4j
public class XmlResolver implements Resolver {
    public void loadConfigurationFromXml(Configuration configuration) {
        //读取xml 配置 dom4j（第三方） 本框架选择jdk自带的
        try (InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("crpc.xml")) {
            if (resourceAsStream != null && resourceAsStream.available() != 0) {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                //读取是禁止dtd文件校验 目前dtd文件非公网，不禁止系统找不到
                factory.setValidating(false);
                //禁用外部实体解析
                factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
                DocumentBuilder documentBuilder = factory.newDocumentBuilder();
                Document document = documentBuilder.parse(resourceAsStream);

                XPathFactory xPathFactory = XPathFactory.newInstance();
                XPath xPath = xPathFactory.newXPath();

                configuration.setPort(resolvePort(document, xPath));
                configuration.setProviderHost(resolveProviderHost(document, xPath));
                configuration.setConsumerHost(resolveConsumerHost(document, xPath));
                configuration.setApplicationName(resolveAppName(document, xPath));
                configuration.setIdGenerator(resolveIdGenerator(document, xPath));
                configuration.setRegistryConfig(resolveRegistryConfig(document, xPath));
                configuration.setSerializerType(resolveSerializerType(document, xPath));

                configuration.setSerializer(resolveSerializer(document, xPath));

                configuration.setCompressorType(resolveCompressorType(document, xPath));
                configuration.setCompressor(resolveCompressor(document, xPath));
                configuration.setLoadBalance(resolveLoadBalance(document, xPath));

                log.info("xml 文件 加载完成");
            }
            log.info("未发现xml 文件 加载完成");
        } catch (Exception e) {
            log.info("xml 文件 加载失败");
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private String resolveConsumerHost(Document document, XPath xPath) {
        return parseAttribute(document, xPath, "/configuration/consumerHost", null);
    }

    private String resolveProviderHost(Document document, XPath xPath) {
        return parseAttribute(document, xPath, "/configuration/providerHost", null);
    }

    private Serializer resolveSerializer(Document document, XPath xPath) {
        try {
            String clazz = parseAttribute(document, xPath, "/configuration/serializer", "class");
            String code = parseAttribute(document, xPath, "/configuration/serializer", "code");
            String type = parseAttribute(document, xPath, "/configuration/serializer", "type");
            Serializer serializer = (Serializer) Class.forName(clazz).getConstructor().newInstance();
            ArrayList<ObjectWrapper<Serializer>> objectWrappers = new ArrayList<>();
            ObjectWrapper<Serializer> serializerObjectWrapper = new ObjectWrapper<>(Byte.valueOf(code), type, serializer);
            objectWrappers.add(serializerObjectWrapper);
            SerializerFactory.addSerializerList(objectWrappers);
            return serializer;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private Compressor resolveCompressor(Document document, XPath xPath) {
        try {
            String clazz = parseAttribute(document, xPath, "/configuration/compressor", "class");
            String code = parseAttribute(document, xPath, "/configuration/compressor", "code");
            String type = parseAttribute(document, xPath, "/configuration/compressor", "type");
            Compressor compressor = (Compressor) Class.forName(clazz).getConstructor().newInstance();
            ArrayList<ObjectWrapper<Compressor>> objectWrappers = new ArrayList<>();
            ObjectWrapper<Compressor> compressorObjectWrapper = new ObjectWrapper<>(Byte.valueOf(code), type, compressor);
            objectWrappers.add(compressorObjectWrapper);
            CompressorFactory.addCompressorList(objectWrappers);
            return compressor;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private LoadBalance resolveLoadBalance(Document document, XPath xPath) {
        String clazz = parseAttribute(document, xPath, "/configuration/loadBalancer", "class");
        try {
            return (LoadBalance) Class.forName(clazz).getConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private String resolveSerializerType(Document document, XPath xPath) {
        return parseAttribute(document, xPath, "/configuration/serializerType", "type");
    }

    private String resolveCompressorType(Document document, XPath xPath) {
        return parseAttribute(document, xPath, "/configuration/compressorType", "type");
    }

    private RegistryConfig resolveRegistryConfig(Document document, XPath xPath) {
        String registry = parseAttribute(document, xPath, "/configuration/registryConfig", "registry");
        String host = parseAttribute(document, xPath, "/configuration/registryConfig", "host");
        String port = parseAttribute(document, xPath, "/configuration/registryConfig", "port");
        return new RegistryConfig(registry + "://" + host + ":" + port);
    }

    private IdGenerationStrategy resolveIdGenerator(Document document, XPath xPath) {
        try {
            String clazz = parseAttribute(document, xPath, "/configuration/idGenerator", "class");
            String dayaCenterId = parseAttribute(document, xPath, "/configuration/idGenerator", "dayaCenterId");
            String machineId = parseAttribute(document, xPath, "/configuration/idGenerator", "MachineId");
            return (IdGenerationStrategy) Class.forName(clazz).getConstructor(new Class[]{long.class, long.class})
                    .newInstance(Long.valueOf(dayaCenterId), Long.valueOf(machineId));
        } catch (Exception e) {
            log.info("读取xml配置出现异常");
            e.printStackTrace();
            throw new RuntimeException();
        }
    }

    private String resolvePort(Document document, XPath xPath) {
        return parseAttribute(document, xPath, "/configuration/port", null);
    }

    private String resolveAppName(Document document, XPath xPath) {
        return parseAttribute(document, xPath, "/configuration/appName", null);
    }

    private String parseAttribute(Document document, XPath xPath, String expression, String attributeName) {
        try {
            if (ObjectUtil.isNull(attributeName)) {
                //从 xml 解析一个节点的文本 返回字符串 <port />77777<port>
                XPathExpression compile = xPath.compile(expression);
                Node node = (Node) compile.evaluate(document, XPathConstants.NODE);
                return node.getTextContent();
            } else {
                //从 xml 解析一个节点属性 返回字符串
                XPathExpression compile = xPath.compile(expression);
                Node node = (Node) compile.evaluate(document, XPathConstants.NODE);
                return node.getAttributes().getNamedItem(attributeName).getNodeValue();
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}
